昨天我們已經成功讓 Todo API 跟 PostgreSQL 容器整合,
今天的目標是讓這整個系統更「像在雲端運作」——
我們要模擬生產部署(production environment),
並練習 docker-compose.prod.yml 的設計與啟動流程。
在開發階段,我們通常會:
npm run dev 自動重啟但在「上線部署」時,這些都不需要。
我們希望:
npm start(production 模式)所以今天要做的事情是:
讓整個專案有「開發用 compose」和「生產用 compose」。
在專案根目錄,改成這樣的 Dockerfile:
# ========== 開發階段 (builder) ==========
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build  # 若是 TypeScript 專案,這會輸出到 dist/
# ========== 生產階段 (runner) ==========
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# 僅複製必要檔案(避免帶入 dev 依賴)
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
這樣會:
docker-compose.prod.yml新增這個檔案:
version: "3.9"
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    image: ts-todo-api:prod
    container_name: ts-todo-api-prod
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/todo
    depends_on:
      - db
    restart: always
  db:
    image: postgres:16-alpine
    container_name: todo-db-prod
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: todo
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: always
volumes:
  db_data:
這份 Compose 就是「生產版」:
restart: always)npm run dev
docker compose -f docker-compose.prod.yml build
這會使用剛剛的 multi-stage Dockerfile,
生成一個更小的 ts-todo-api:prod image。
你可以檢查大小:
docker images ts-todo-api:prod
docker compose -f docker-compose.prod.yml up -d
檢查容器狀態:
docker compose -f docker-compose.prod.yml ps
輸出:
NAME               SERVICE  STATUS    PORTS
ts-todo-api-prod   api      running   0.0.0.0:3000->3000/tcp
todo-db-prod       db       running   0.0.0.0:5432->5432/tcp
docker compose -f docker-compose.prod.yml logs -f
會看到:
🚀 Server running on port 3000
Database connected successfully!
一切順利 🎉
模擬一次「服務重啟」的情境:
docker compose -f docker-compose.prod.yml restart api
模擬「整個系統重新上線」:
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d
因為有 restart: always
就算伺服器重啟或 Docker daemon 重新啟動,
容器也會自動復原。
在生產環境中,你可以加入 healthcheck:
  api:
    ...
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/todos"]
      interval: 30s
      timeout: 5s
      retries: 3
這樣 Docker 會定期檢查 API 是否正常,
若失敗就標記為 unhealthy。
ts-todo-api/
├─ src/
│  └─ index.ts
├─ prisma/
│  └─ schema.prisma
├─ Dockerfile
├─ docker-compose.yml         # 開發用
├─ docker-compose.prod.yml    # 生產用
├─ package.json
└─ .env
開發用:
docker compose up
生產用:
docker compose -f docker-compose.prod.yml up -d
| 項目 | 開發版 | 生產版 | 
|---|---|---|
| 啟動指令 | npm run dev | 
npm start | 
| 是否掛 volume | ✅(方便即時修改) | ❌(固定程式) | 
| 是否使用 cache | ✅ | ✅(build cache) | 
| 是否含 devDependencies | ✅ | ❌ | 
| 映像大小 | 約 350MB | 約 180MB | 
| 重啟策略 | 無 | restart: always | 
這樣在真正部署到雲端(例如 EC2、VPS、GCP Run)時,
就能直接使用生產版的 Compose。
今天的練習就像是正式走上「部署舞台」。
我學會了:
docker-compose.prod.yml 的設計邏輯-f 切換不同環境設定restart、healthcheck、logs 的部署操作整個感覺就像從「學生作品」變成「正式產品」:
容器小、啟動快、重啟穩。